"""
主程序模块：实现刷题系统的核心功能
包含主应用程序类和所有业务逻辑处理
"""

import customtkinter as ctk
from CTkMessagebox import CTkMessagebox
from document_parser import DocumentParser
from ui_components import QuizUI
import json
import os
from openai import OpenAI
from docx import Document
from docx.shared import Pt
import threading
import time
from config import WINDOW_CONFIG, THEMES, FONTS, AI_CONFIG, FILE_TYPES

class HackerQuizApp:
    """
    刷题系统的主应用程序类
    
    负责处理核心业务逻辑，包括：
    - 题目的加载和管理
    - 答案的检查和评分
    - 错题本的管理
    - AI辅助功能
    - 主题切换
    """
    
    def __init__(self):
        """初始化应用程序"""
        # 创建主窗口
        self.root = ctk.CTk()
        self.root.title(WINDOW_CONFIG["title"])
        self.root.geometry(WINDOW_CONFIG["size"])
        
        # 设置主题
        self.themes = THEMES
        self.current_theme = "黑色主题"
        self.apply_theme(self.current_theme)
        
        # 设置默认字体和按钮大小
        self.default_font = FONTS["default"]
        self.button_height = 40
        
        # 初始化OpenAI客户端
        self.client = OpenAI(
            api_key="sk-rbrNVJKLkOoALHzsAYyUtxD86Ap1YVvNlOoncZHGhFUDNyaQ",
            base_url="https://api.moonshot.cn/v1",
            timeout=20.0
        )
        
        # 初始化数据
        self.current_question_index = 0
        self.questions = []
        self.answers = []
        self.explanations = []
        self.selected_option = ctk.StringVar()
        self.parser = DocumentParser()
        self.wrong_questions = self.load_wrong_questions()
        self.marked_questions = self.load_marked_questions()
        
        # 加载动画相关
        self.loading_dots = 0
        self.loading_animation = None
        
        # 初始化UI
        self.ui = QuizUI(self)
    
    def apply_theme(self, theme_name):
        """
        应用主题设置
        
        参数:
            theme_name: 主题名称
        """
        theme = self.themes[theme_name]
        ctk.set_appearance_mode(theme["mode"])
        ctk.set_default_color_theme(theme["theme"])
        self.current_theme = theme_name
        
        if hasattr(self, 'ui'):
            # 更新UI组件的主题
            self._update_ui_theme(theme)
    
    def _update_ui_theme(self, theme):
        """
        更新UI组件的主题设置
        
        参数:
            theme: 主题配置字典
        """
        # 更新按钮颜色
        for btn in self.ui.question_buttons:
            btn.configure(
                fg_color=theme["button_color"],
                text_color=theme["text_color"]
            )
        
        # 更新文本显示区域
        self.ui.question_display.configure(
            text_color=theme["text_color"]
        )
        
        # 更新主题选择器
        self.ui.theme_var.set(self.current_theme)
        
        # 更新所有功能按钮
        buttons = [
            self.ui.upload_btn,
            self.ui.wrong_btn,
            self.ui.prev_btn,
            self.ui.next_btn,
            self.ui.similar_btn,
            self.ui.ai_help_btn,
            self.ui.show_answer_btn
        ]
        for btn in buttons:
            btn.configure(
                fg_color=theme["button_color"],
                text_color=theme["text_color"]
            )
    
    def upload_file(self):
        """处理文件上传"""
        file_path = ctk.filedialog.askopenfilename(
            filetypes=FILE_TYPES["word"],
            title="选择题目文档"
        )
        if file_path:
            self.load_questions(file_path)
    
    def load_questions(self, file_path):
        """
        加载题目文件
        
        参数:
            file_path: 文件路径
        """
        try:
            self.questions, self.answers, self.explanations = self.parser.parse_document(file_path)
            self.current_question_index = 0
            self.update_question_display()
            self.ui.update_question_buttons(self.questions)
            self.ui.update_button_states(self.current_question_index)
        except Exception as e:
            CTkMessagebox(title="错误", message=f"加载文档失败: {str(e)}")
    
    def jump_to_question(self, index):
        self.current_question_index = index
        self.update_question_display()
        self.ui.update_button_states(index)
    
    def update_question_display(self):
        if 0 <= self.current_question_index < len(self.questions):
            self.ui.question_display.delete("1.0", "end")
            self.ui.question_display.insert("1.0", self.questions[self.current_question_index])
            self.ui.answer_display.delete("1.0", "end")
            self.selected_option.set("")
    
    def prev_question(self):
        if self.current_question_index > 0:
            self.current_question_index -= 1
            self.update_question_display()
            self.ui.update_button_states(self.current_question_index)
    
    def next_question(self):
        if self.current_question_index < len(self.questions) - 1:
            self.current_question_index += 1
            self.update_question_display()
            self.ui.update_button_states(self.current_question_index)
    
    def toggle_answer(self):
        if not self.questions or self.current_question_index >= len(self.questions):
            return
            
        current_answer = self.answers[self.current_question_index]
        current_explanation = self.explanations[self.current_question_index]
        
        if not self.ui.answer_display.get("1.0", "end").strip():
            self.ui.answer_display.delete("1.0", "end")
            self.ui.answer_display.insert("1.0", f"{current_answer}\n\n解析：\n{current_explanation}")
            self.ui.answer_display.configure(text_color="black")
        else:
            self.ui.answer_display.delete("1.0", "end")
    
    def check_answer(self):
        if not self.selected_option.get():
            return
        
        current_answer = self.answers[self.current_question_index]
        selected = self.selected_option.get()
        current_question = self.questions[self.current_question_index]
        current_explanation = self.explanations[self.current_question_index]
        
        self.ui.answer_display.delete("1.0", "end")
        if f"答案：{selected}" == current_answer:
            self.ui.answer_display.insert("1.0", "✓ 回答正确！\n" + current_answer + "\n\n解析：\n" + current_explanation)
            self.ui.answer_display.configure(text_color="green")
            if current_question in self.wrong_questions:
                self.wrong_questions.remove(current_question)
                self.save_wrong_questions()
            self.root.after(1000, self.next_question)
        else:
            self.ui.answer_display.insert("1.0", "✗ 回答错误！\n" + current_answer + "\n\n解析：\n" + current_explanation)
            self.ui.answer_display.configure(text_color="red")
            if current_question not in self.wrong_questions:
                self.wrong_questions.append(current_question)
                self.save_wrong_questions()
    
    def load_wrong_questions(self):
        try:
            if os.path.exists("wrong_questions.json"):
                with open("wrong_questions.json", "r", encoding="utf-8") as f:
                    return json.load(f)
        except Exception as e:
            print(f"加载错题本失败: {e}")
        return []
    
    def save_wrong_questions(self):
        try:
            with open("wrong_questions.json", "w", encoding="utf-8") as f:
                json.dump(self.wrong_questions, f, ensure_ascii=False, indent=2)
        except Exception as e:
            print(f"保存错题本失败: {e}")
    
    def load_marked_questions(self):
        try:
            if os.path.exists("marked_questions.json"):
                with open("marked_questions.json", "r", encoding="utf-8") as f:
                    return json.load(f)
        except Exception as e:
            print(f"加载标记题目失败: {e}")
        return []
    
    def save_marked_questions(self):
        try:
            with open("marked_questions.json", "w", encoding="utf-8") as f:
                json.dump(self.marked_questions, f, ensure_ascii=False, indent=2)
        except Exception as e:
            print(f"保存标记题目失败: {e}")
    
    def toggle_mark_question(self):
        if not self.questions or self.current_question_index >= len(self.questions):
            return
            
        current_question = self.questions[self.current_question_index]
        if current_question in self.marked_questions:
            self.marked_questions.remove(current_question)
        else:
            self.marked_questions.append(current_question)
        self.save_marked_questions()
        self.ui.update_button_states(self.current_question_index)
    
    def show_wrong_questions(self):
        self.show_question_list("错题本", self.wrong_questions)
    
    def show_marked_questions(self):
        self.show_question_list("标记题目", self.marked_questions)
    
    def show_question_list(self, title, question_list):
        list_window = ctk.CTkToplevel(self.root)
        list_window.title(title)
        list_window.geometry("800x600")
        
        if not question_list:
            message = "目前没有错题记录！继续保持！" if title == "错题本" else "目前没有标记题目！"
            label = ctk.CTkLabel(
                list_window,
                text=message,
                font=self.default_font
            )
            label.pack(pady=20)
            
            close_btn = ctk.CTkButton(
                list_window,
                text="关闭",
                command=list_window.destroy,
                font=self.default_font,
                height=35
            )
            close_btn.pack(pady=5)
            return
        
        left_frame = ctk.CTkFrame(list_window)
        left_frame.pack(side="left", fill="y", padx=10, pady=10)
        
        right_frame = ctk.CTkFrame(list_window)
        right_frame.pack(side="right", fill="both", expand=True, padx=10, pady=10)
        
        for i, question in enumerate(question_list, 1):
            btn = ctk.CTkButton(
                left_frame,
                text=f"题目 {i}",
                command=lambda q=question: self.show_question_detail(right_frame, q),
                font=self.default_font,
                height=35,
                width=150
            )
            btn.pack(pady=2)
        
        button_frame = ctk.CTkFrame(list_window)
        button_frame.pack(side="bottom", fill="x", padx=10, pady=5)
        
        export_btn = ctk.CTkButton(
            button_frame,
            text="导出到Word",
            command=lambda: self.export_questions(title, question_list),
            font=self.default_font,
            height=35
        )
        export_btn.pack(side="left", padx=5)
        
        if title == "错题本":
            clear_btn = ctk.CTkButton(
                button_frame,
                text="清空错题本",
                command=lambda: self.clear_questions(list_window, "wrong"),
                fg_color="#ff0000",
                text_color="white",
                font=self.default_font,
                height=35
            )
            clear_btn.pack(side="left", padx=5)
        else:
            clear_btn = ctk.CTkButton(
                button_frame,
                text="清空标记",
                command=lambda: self.clear_questions(list_window, "marked"),
                fg_color="#ff0000",
                text_color="white",
                font=self.default_font,
                height=35
            )
            clear_btn.pack(side="left", padx=5)
        
        close_btn = ctk.CTkButton(
            button_frame,
            text="关闭",
            command=list_window.destroy,
            font=self.default_font,
            height=35
        )
        close_btn.pack(side="right", padx=5)
    
    def show_question_detail(self, frame, question):
        for widget in frame.winfo_children():
            widget.destroy()
        
        question_index = self.questions.index(question)
        answer = self.answers[question_index] if question_index >= 0 else "未找到答案"
        explanation = self.explanations[question_index] if question_index >= 0 else ""
        
        detail_text = ctk.CTkTextbox(
            frame,
            font=self.default_font,
            wrap="word"
        )
        detail_text.pack(fill="both", expand=True, padx=10, pady=10)
        detail_text.insert("end", f"{question}\n\n{answer}\n\n解析：\n{explanation}")
        
        jump_btn = ctk.CTkButton(
            frame,
            text="跳转到此题",
            command=lambda: [self.jump_to_question(question_index), frame.winfo_toplevel().destroy()],
            font=self.default_font,
            height=35
        )
        jump_btn.pack(pady=5)
    
    def clear_questions(self, window, question_type):
        if question_type == "wrong":
            self.wrong_questions = []
            self.save_wrong_questions()
        else:
            self.marked_questions = []
            self.save_marked_questions()
        window.destroy()
    
    def export_questions(self, title, question_list):
        if not question_list:
            return
            
        file_path = ctk.filedialog.asksaveasfilename(
            defaultextension=".docx",
            filetypes=[("Word文档", "*.docx")],
            title=f"导出{title}"
        )
        
        if file_path:
            doc = Document()
            doc.add_heading(title, 0)
            
            for i, question in enumerate(question_list, 1):
                question_index = self.questions.index(question)
                answer = self.answers[question_index] if question_index >= 0 else "未找到答案"
                explanation = self.explanations[question_index] if question_index >= 0 else ""
                
                doc.add_paragraph(f"题目 {i}:")
                doc.add_paragraph(question)
                doc.add_paragraph(answer)
                doc.add_paragraph("解析：")
                doc.add_paragraph(explanation)
                doc.add_paragraph("\n")
            
            doc.save(file_path)
    
    def update_loading_animation(self, loading_label):
        if loading_label.winfo_exists():
            dots = "." * self.loading_dots
            loading_label.configure(text=f"正在分析题目{dots}")
            self.loading_dots = (self.loading_dots + 1) % 4
            self.loading_animation = self.root.after(500, lambda: self.update_loading_animation(loading_label))
    
    def stop_loading_animation(self):
        if self.loading_animation:
            self.root.after_cancel(self.loading_animation)
            self.loading_animation = None
            self.loading_dots = 0
    
    def get_ai_help(self):
        if not self.questions or self.current_question_index >= len(self.questions):
            return
            
        current_question = self.questions[self.current_question_index]
        current_answer = self.answers[self.current_question_index]
        
        ai_window, loading_frame, loading_label = self.ui.create_ai_help_window()
        ai_window.title("Kimi解析")
        
        self.update_loading_animation(loading_label)
        
        def ai_analysis_thread():
            try:
                prompt = f"""请帮我解析这道题目，包括：
1. 正确答案是什么，为什么这个选项是正确的
2. 其他选项为什么是错误的
3. 这道题目考察的知识点
4. 请创建一道类似的题目（包含题干、四个选项和答案）

题目：{current_question}
正确答案：{current_answer}
"""
                
                completion = self.client.chat.completions.create(
                    model="moonshot-v1-8k",
                    messages=[
                        {"role": "system", "content": "你是一个专业的题目解析助手Kimi，善于分析题目的考点和答案原理，并能创建类似的题目帮助学习。"},
                        {"role": "user", "content": prompt}
                    ],
                    temperature=0.7,
                )
                
                self.root.after(0, lambda: self.show_ai_result(ai_window, loading_frame, completion.choices[0].message.content))
                
            except Exception as e:
                self.root.after(0, lambda: self.show_ai_error(ai_window, loading_frame, str(e)))
        
        threading.Thread(target=ai_analysis_thread, daemon=True).start()
    
    def show_ai_result(self, ai_window, loading_frame, content):
        self.stop_loading_animation()
        loading_frame.destroy()
        
        result_frame = ctk.CTkFrame(ai_window)
        result_frame.pack(fill="both", expand=True, padx=10, pady=10)
        
        title_label = ctk.CTkLabel(
            result_frame,
            text="Kimi解析结果",
            font=("SimSun", 20, "bold")
        )
        title_label.pack(pady=5)
        
        ai_text = ctk.CTkTextbox(
            result_frame,
            width=580,
            height=400,
            font=self.default_font,
            wrap="word"
        )
        ai_text.pack(padx=10, pady=5)
        ai_text.insert("1.0", content)
        
        button_frame = ctk.CTkFrame(result_frame)
        button_frame.pack(fill="x", pady=5)
        
        export_btn = ctk.CTkButton(
            button_frame,
            text="导出解析",
            command=lambda: self.export_ai_analysis(content),
            font=self.default_font,
            height=35,
            width=100
        )
        export_btn.pack(side="left", padx=5)
        
        close_btn = ctk.CTkButton(
            button_frame,
            text="关闭",
            command=ai_window.destroy,
            font=self.default_font,
            height=35,
            width=100
        )
        close_btn.pack(side="right", padx=5)
    
    def show_ai_error(self, ai_window, loading_frame, error_message):
        self.stop_loading_animation()
        loading_frame.destroy()
        
        error_label = ctk.CTkLabel(
            ai_window,
            text=f"获取AI解析失败：{error_message}",
            font=self.default_font,
            text_color="red"
        )
        error_label.pack(pady=20)
        
        retry_btn = ctk.CTkButton(
            ai_window,
            text="重试",
            command=lambda: [ai_window.destroy(), self.get_ai_help()],
            font=self.default_font,
            height=35
        )
        retry_btn.pack(pady=10)
    
    def export_ai_analysis(self, analysis_content):
        file_path = ctk.filedialog.asksaveasfilename(
            defaultextension=".docx",
            filetypes=[("Word文档", "*.docx")],
            title="导出Kimi解析"
        )
        
        if file_path:
            doc = Document()
            doc.add_heading("Kimi题目解析", 0)
            
            doc.add_heading("原题", level=1)
            doc.add_paragraph(self.questions[self.current_question_index])
            doc.add_heading("正确答案", level=1)
            doc.add_paragraph(self.answers[self.current_question_index])
            
            doc.add_heading("详细解析", level=1)
            doc.add_paragraph(analysis_content)
            
            doc.save(file_path)
    
    def generate_similar_questions(self):
        if not self.questions or self.current_question_index >= len(self.questions):
            return
            
        current_question = self.questions[self.current_question_index]
        current_answer = self.answers[self.current_question_index]
        
        similar_window = ctk.CTkToplevel(self.root)
        similar_window.title("同类型题目生成")
        similar_window.geometry("800x600")
        
        loading_frame = ctk.CTkFrame(similar_window)
        loading_frame.pack(pady=20)
        
        loading_label = ctk.CTkLabel(
            loading_frame,
            text="正在生成同类型题目...",
            font=self.default_font
        )
        loading_label.pack(pady=5)
        
        progress = ctk.CTkProgressBar(loading_frame)
        progress.pack(pady=5)
        progress.configure(mode="indeterminate")
        progress.start()
        
        self.update_loading_animation(loading_label)
        
        def generate_questions_thread():
            try:
                prompt = f"""请根据以下题目生成3道相似的题目，要求：
1. 考察相同的知识点
2. 难度相当
3. 每道题都包含4个选项(A、B、C、D)
4. 给出正确答案和详细解析

原题：{current_question}
原题答案：{current_answer}

请按以下格式生成：
第1题：
[题目内容]
A. [选项A]
B. [选项B]
C. [选项C]
D. [选项D]
答案：[答案字母]
解析：[解析内容]

第2题：
...（同上格式）

第3题：
...（同上格式）"""
                
                completion = self.client.ChatCompletion.create(
                    model="moonshot-v1-8k",
                    messages=[
                        {"role": "system", "content": "你是一个专业的题目生成助手，善于创建相似的题目帮助学习。"},
                        {"role": "user", "content": prompt}
                    ],
                    temperature=0.8,
                )
                
                self.root.after(0, lambda: self.show_similar_questions(similar_window, loading_frame, completion.choices[0].message.content))
                
            except Exception as e:
                error_msg = str(e)
                self.root.after(0, lambda: self.show_ai_error(similar_window, loading_frame, error_msg))
        
        threading.Thread(target=generate_questions_thread, daemon=True).start()
    
    def show_similar_questions(self, window, loading_frame, content):
        self.stop_loading_animation()
        loading_frame.destroy()
        
        result_frame = ctk.CTkFrame(window)
        result_frame.pack(fill="both", expand=True, padx=10, pady=10)
        
        title_label = ctk.CTkLabel(
            result_frame,
            text="同类型题目",
            font=("SimSun", 20, "bold")
        )
        title_label.pack(pady=5)
        
        questions_text = ctk.CTkTextbox(
            result_frame,
            width=780,
            height=450,
            font=self.default_font,
            wrap="word"
        )
        questions_text.pack(padx=10, pady=5)
        questions_text.insert("1.0", content)
        
        button_frame = ctk.CTkFrame(result_frame)
        button_frame.pack(fill="x", pady=5)
        
        export_btn = ctk.CTkButton(
            button_frame,
            text="导出题目",
            command=lambda: self.export_similar_questions(content),
            font=self.default_font,
            height=35,
            width=100
        )
        export_btn.pack(side="left", padx=5)
        
        close_btn = ctk.CTkButton(
            button_frame,
            text="关闭",
            command=window.destroy,
            font=self.default_font,
            height=35,
            width=100
        )
        close_btn.pack(side="right", padx=5)
    
    def export_similar_questions(self, questions_content):
        file_path = ctk.filedialog.asksaveasfilename(
            defaultextension=".docx",
            filetypes=[("Word文档", "*.docx")],
            title="导出同类型题目"
        )
        
        if file_path:
            doc = Document()
            doc.add_heading("同类型练习题", 0)
            
            doc.add_heading("原题", level=1)
            doc.add_paragraph(self.questions[self.current_question_index])
            doc.add_paragraph(self.answers[self.current_question_index])
            
            doc.add_heading("同类型题目", level=1)
            doc.add_paragraph(questions_content)
            
            doc.save(file_path)
    
    def run(self):
        """启动应用程序"""
        self.root.mainloop()

if __name__ == "__main__":
    app = HackerQuizApp()
    app.run() 
# python main.py